
#ifndef TRACE_AS_ROUTE_H
#define TRACE_AS_ROUTE_H

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <linux/ip.h>
#include <linux/icmp.h>
#include <unistd.h>
#include <iostream>
#include <string>
#include <cstring>
#include <net/if.h>
#include <sys/ioctl.h>
#include "Utility.h"
#include <fstream>

#define MAX_TTL 64
#define ECHO_TIMEOUT 3
#define ECHO_INTERVAL 1




int TraceASRoute(std::string dstAddr)
{
    struct iphdr *ip, *ip_reply;
    struct icmphdr *icmp, *icmp_reply;
    struct sockaddr_in connection;
    char* packet;
    char* buffer;
    int sockfd;
    
	std::string srcAddr;
	GetSourceIP(srcAddr);

	std::cout << "Source address: " << srcAddr << std::endl;
	std::cout << "Destination address: " << dstAddr << std::endl << std::endl;
    
  	packet = new char[sizeof(struct iphdr) + sizeof(struct icmphdr)];
	buffer = new char[sizeof(struct iphdr) + sizeof(struct icmphdr)];

    ip = (struct iphdr*) packet;
    icmp = (struct icmphdr*) (packet + sizeof(struct iphdr));


	//
	// The ICMP packet
	//
    icmp->type			= ICMP_ECHO;
    icmp->code			= 0; // error message code, none = 0
    icmp->un.echo.id		= 0;
    icmp->un.echo.sequence	= 0; // sequence # if more than one are sent
    icmp-> checksum		= in_cksum((unsigned short *)icmp, sizeof(struct icmphdr));

	//
	// The IP header
	//
    ip->ihl			= 5; // header length = 5*4 = 20 bytes
    ip->version			= 4; // IPv4
    ip->tos			= 0; // normal service
    ip->tot_len			= sizeof(struct iphdr) + sizeof(struct icmphdr);
    ip->id			= htons(random()); // single datagram, ID doesn't matter
    //ip->ttl			= 255; // max is 255
    ip->protocol		= IPPROTO_ICMP;
    ip->saddr			= inet_addr(srcAddr.c_str());
    ip->daddr			= inet_addr(dstAddr.c_str());
    //ip->check			= in_cksum((unsigned short *)ip, sizeof(struct iphdr));
 
    

   
    connection.sin_family = AF_INET;
    connection.sin_addr.s_addr = inet_addr(dstAddr.c_str());
	socklen_t addrlen;

	int selectVal, whoisVal;
	unsigned int echoCounter, timeoutCounter = 0;
	for(echoCounter = 0; echoCounter < MAX_TTL; ++echoCounter)	
	{
		sleep(ECHO_INTERVAL);

		ip->ttl = echoCounter+1;
		ip->check = in_cksum((unsigned short*)ip, sizeof(struct iphdr));


		//
		// create raw socket each iteration
		// otherwise the server doesn't seem to respond
		// something to do with the port, echo.id and echo.seq
		//
		if ((sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)) == -1)
		{
			perror("socket");
			exit(EXIT_FAILURE);
		}
		
		// notify the lower layers of the custom headers    
		int optval = 1;
		if(setsockopt(sockfd, IPPROTO_IP, IP_HDRINCL, &optval, sizeof(int)) == -1)
		{
			std::cout << "Unable to set the IP_HDRINCL option. Exiting..." << std::endl;
			close(sockfd);
			return -1;
		}


  		// send packet   
		int snd = sendto(sockfd, packet, ip->tot_len, 0, (struct sockaddr *)&connection, sizeof(struct sockaddr));
		if(snd == -1)
		{
			perror("sendto");
			close(sockfd);
			return -1;
		}
		/*else
			std::cout << "Sent " << snd << " bytes of attempt# " << echoCounter << " to " << dstAddr << std::endl;
		*/
		fd_set fds;
		FD_ZERO(&fds);
		FD_SET(sockfd, &fds);
   		struct timeval tv;
		tv.tv_sec = ECHO_TIMEOUT;
		tv.tv_usec = 0;
		selectVal = select(sockfd+1, &fds, NULL, NULL, &tv);
		if(selectVal == -1)
		{
			perror("select");
			close(sockfd);
			return -1;
		}
		else if(selectVal == 0)
		{
			std::cout << "Timeout occured for attempt# " << echoCounter << std::endl;
			close(sockfd);
		
			++timeoutCounter;
			if(timeoutCounter == 5) // stop after a while
				break;
			
			continue;
		}
	
		// select did not timeout this time
		timeoutCounter = 0;
	
		addrlen = sizeof(connection);
		int recv = recvfrom(sockfd, buffer, sizeof(struct iphdr) + sizeof(struct icmphdr), 0, (struct sockaddr *)&connection, &addrlen);
		if (recv == -1)
		{
			perror("recv");
		}
		else if (recv == 0)
		{
			std::cout << "Remote side closed the socket!" << std::endl;
			return -1;
		}
		else
		{
			std::string replyerIP(inet_ntoa(((struct sockaddr_in*)&connection)->sin_addr));
			//std::cout << "Received " << recv << " byte reply from " << replyerIP.c_str() << std::endl;
			ip_reply = (struct iphdr*) buffer;
			icmp_reply = (struct icmphdr*) (buffer + sizeof(struct iphdr));
			//std::cout << "ID: " << ntohs(ip_reply->id) << std::endl;
			//std::cout << "TTL: " << (int)ip_reply->ttl << std::endl;
			//std::cout << "ICMP type: " << (int)icmp_reply->type << std::endl;

			// now do whois on the replyerIP
			char cmd[500];
			sprintf(cmd, "whois -h whois.cymru.com %s > whoisop%02d", replyerIP.c_str(), echoCounter);
			//std::cout << "Executing: " << cmd << std::endl;
			whoisVal = system(cmd);

			if((int)icmp_reply->type == ICMP_ECHOREPLY)
				break;
		}
    	close(sockfd);
	}
	
	// echoCounter now contains the number of whois output files
	char fileName[20];
	char line0[500];
	char line1[500];
	int previousAS = 0, newAS = 0;
	size_t pos;
	const char SEPARATOR = '|';
	char cmd[50];

	for(unsigned int whoisCounter = 0; whoisCounter <= echoCounter; ++whoisCounter)
	{
		sprintf(fileName, "whoisop%02d", whoisCounter);
		std::ifstream whoisFile(fileName);
		whoisFile.getline(line0, 200);
		whoisFile.getline(line1, 200);
		//std::cout << line0 << std::endl << line1 << std::endl;
		std::string line1Str(line1);
		pos = line1Str.find_first_of(SEPARATOR);
		newAS = atoi(line1Str.substr(0, pos-1).c_str());
		
		if(newAS != previousAS)
		{
			pos = line1Str.find_last_of(SEPARATOR);
			std::cout << "***** " << newAS << "\t\t" 
				<< line1Str.substr(pos+1) << " *****" << std::endl;
			previousAS = newAS;
		}
		whoisFile.close();

		// comment out the below two lines if you need the whois output files
		sprintf(cmd, "rm %s", fileName);
		system(cmd);
	}

	// delete the dynamically allocated memory
	delete[] packet;
	delete[] buffer;

    return 0;
}



#endif // TRACE_AS_ROUTE_H
